package com.gitee.fantaros.web.fan.netcut.threading;


import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.function.Supplier;

public class SyncProcessManager {

    @FunctionalInterface
    protected interface ThreadMaker {
        Thread makeRunningThread(CountDownLatch countDownLatch, Presentable<Exception> anyException);
    }

    private <T> T getValueElseThrow (Presentable<T> resultWrapper, Presentable<Exception> anyException)
            throws Exception {
        if (anyException.isPresent()) {
            throw anyException.get();
        }
        if (resultWrapper.isPresent()) {
            return resultWrapper.get();
        } else {
            throw new NoSuchElementException("No Result returned");
        }
    }

    private <T> T runForResult(ThreadMaker threadMaker,
                               ResultCallback<T> getValueOrThrowException,
                               Syncable<T, String> syncable, Presentable<T> valueWrapper) throws Exception {
        Presentable<Exception> anyException = new Presentable<>(null);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        syncable.setCountDownLatch(countDownLatch);
        Thread runnerThread;
        runnerThread = threadMaker.makeRunningThread(countDownLatch, anyException);
        runnerThread.start();
        countDownLatch.await();
        return getValueOrThrowException.getValue(valueWrapper, anyException);
    }

    public <T> T run(Supplier<Presentable<T>> ctor, Syncable<T, String> syncable)
            throws Exception {
        Presentable<T> wrapper = ctor.get();
        return runForResult((countDownLatch, anyexception) -> {
            syncable.setCountDownLatch(countDownLatch);
            return new Thread(() -> {
                try {
                    wrapper.set(syncable.call());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }, this::getValueElseThrow, syncable, wrapper);
    }

    public <T> T run(DefaultSyncable<T> defaultSyncable)
            throws Exception {
        Presentable<T> wrapper = new Presentable<>();
        return runForResult((countDownLatch, anyexception) -> {
            defaultSyncable.setCountDownLatch(countDownLatch);
            return new Thread(() -> {
                try {
                    wrapper.set(defaultSyncable.call());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }, this::getValueElseThrow, defaultSyncable, wrapper);
    }

}
